﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace NetConnector.Net.Server
{
    public class Server
    {
        #region Private
        private int _commandport;
        private int _dataport;
        private IPAddress _ip;
        private TcpClient _c_c;
        private TcpClient _c_s;

        private TcpListener _l_c;
        private TcpListener _l_s;

        private string _command;
        private bool _isconnected;
        private List<IPAddress> Ia = new List<IPAddress>();
        private Thread commandT;
        private Thread ipT;

        //need a read buffer size
        private Byte[] b = new Byte[256];
        private string data = null;
        private DataArguments _data = new DataArguments();
        #endregion

        #region Public Properties
        /// <summary>
        /// Returns the bound port
        /// </summary>
        public int CommandPort { get { return _commandport; } }

        /// <summary>
        /// Returns the bound IP
        /// </summary>
        public IPAddress IP { get { return _ip; } }

        /// <summary>
        /// Returns True|False if client is Connected|Not Connected
        /// </summary>
        public bool IsConnected { get { return _isconnected; } }

        /// <summary>
        /// String command
        /// </summary>
        public string Command { set { _command = value; } }
        #endregion



        /// <summary>
        /// Creates and Starts a client service on the designated port and targets the specified host computer.
        /// </summary>
        /// <param name="CommandPort">Port to send and recieve commands on.  Must between 1024 and 65535</param>
        /// <param name="DataPort">Port to send and recieve data on</param>
        /// <param name="host">The host computer to which you want to connect</param>
        /// <param name="Connections">The total number of simultaneuos connections allowed</param>
        public Server(int CommandPort, int DataPort, int Connections)
        {
            _isconnected = false;
            if (CommandPort < 1024 || CommandPort > 65535)
            {
                throw new ArgumentOutOfRangeException("command port", "Value must be between 1024 and 65535.");
            }
            if (DataPort < 1024 || DataPort > 65535)
            {
                throw new ArgumentOutOfRangeException("data port", "Value must be between 1024 and 65535.");
            }
            if (Connections == 0)
            {
                throw new ArgumentOutOfRangeException("Number of connections can not be zero");
            }
            try
            {
                _commandport = CommandPort;
                _dataport = DataPort;


                _l_c = new TcpListener(IPAddress.Any, _commandport);
                _l_s = new TcpListener(IPAddress.Any, _dataport);

                commandT = new Thread(new ParameterizedThreadStart(ProcessCommand));
                ipT = new Thread(new ParameterizedThreadStart(ProcessIp));
            }
            catch (SocketException ex)
            {
                throw new SystemException("Socket error", ex);
            }
        }
        /// <summary>
        /// Starts the client services
        /// </summary>
        public void Start()
        {
            if (!_isconnected)
            {
                _l_s.Start();
                _l_c.Start();
                // listenT.Start();
                _l_c.BeginAcceptTcpClient(new AsyncCallback(OnCommand), _l_c);
                _l_s.BeginAcceptTcpClient(new AsyncCallback(OnCommand), _l_s);
                // use the next line for data, currently broken
                //_l_s.BeginAcceptTcpClient(new AsyncCallback(OnRecieveData), _l_s);
            }
        }

        /// <summary>
        /// Connection handler
        /// </summary>
        /// <exception cref="SystemException">Throws an exception with the InnerException passed from the client</exception>
        private void hConnection()
        {
            try
            {
                _c_c = _l_s.AcceptTcpClient();
                _c_s = _l_c.AcceptTcpClient();
                _isconnected = true;
            }
            catch (SocketException ex)
            {
                _c_c.Close();
                _c_s.Close();
                _l_c.Stop();
                _l_s.Stop();
                commandT.Abort();
                ipT.Abort();
                throw new SystemException("Tcp Client connection problem.", ex.InnerException);
            }
        }

        /// <summary>
        /// Handles the Sending of data
        /// </summary>
        /// <param name="a"></param>
        private void hSend(object a)
        {
            //Byte[] send;
            //    using (MemoryStream ms = new MemoryStream())
            //    {
            //        BinaryFormatter bf = new BinaryFormatter();
            //        try { bf.Serialize(ms, data); }
            //        catch { return; }
            //        send = ms.ToArray();
            //}
            try
            {
                TcpClient serverc = (TcpClient)a;
                NetworkStream serverstream = serverc.GetStream();
                if (serverstream.CanRead)
                {
                    int bytesread;
                    while (true)
                    {
                        Byte[] incoming = new byte[1024];
                        bytesread = 0;
                        try //getting actual siize of bytes from the stream
                        {
                            bytesread = serverstream.Read(incoming, 0, incoming.Length);
                            MemoryStream ms = new MemoryStream(incoming);
                            BinaryFormatter f = new BinaryFormatter();
                            ms.Write(incoming, 0, bytesread);
                            ms.Position = 0;
                            _data = (DataArguments)f.Deserialize(ms);
                        }
                        catch
                        {
                            throw new SystemException("Recieving data error.");
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw new SystemException("Error getting network stream.");
            }
        }

        private void ProcessCommand(object o)
        {
            DataArguments com;
            try
            {
                com = (DataArguments)o;
            }
            catch { throw new InvalidCastException("Could not get string object"); }
            switch (com.DataArgs)
            {
                default:
                    return;

                case "remove":
                    Ia.Remove(IPAddress.Parse(com.Data));
                    break;
            }
        }

        private void ProcessIp(object o)
        {
            IPAddress ip;
            try
            {
                ip = (IPAddress)o;
            }
            catch
            {
                throw new InvalidCastException("Could not cast to an IP address");
            }
            if (!Ia.Contains(ip))
            {
                Ia.Add(ip);
            }
        }
        //private void announce(string ip)
        //{
        //    if (_isconnected == true)       // check is the server is connected
        //    {
        //        sendT.Start(_c_c); // Start the Capture thread as soon as capture is requested
        //        NetworkStream ns = _c_c.GetStream();  // get the stream from server
        //        if (ns.CanWrite == true)           // check if we can write to server stream
        //        {
        //            ASCIIEncoding encoder = new ASCIIEncoding();
        //            byte[] command = encoder.GetBytes(ip);  // convert the word capture into byte
        //            ns.Write(command, 0, command.Length);       // send it to the server stream
        //            ns.Flush();
        //        }
        //    }
        //}

        private void OnCommand(IAsyncResult a)
        {
            TcpListener li = (TcpListener)a.AsyncState;
            TcpClient cl = li.EndAcceptTcpClient(a);
            string command = "";
            if (_isconnected == true)                            // check is the server is connected
            {
                //captureThread.Abort();                         // Suspend the Capture 
                NetworkStream ns = _c_c.GetStream();       // get the stream from server
                if (ns.CanRead)                         // check if we can write to server stream
                {
                    Byte[] incoming = new byte[256];
                    int i;
                    while ((i = ns.Read(incoming, 0, incoming.Length)) != 0)
                    {
                        command = Encoding.ASCII.GetString(incoming, 0, i);

                    }
                }
            }
            ipT.Start(((IPEndPoint)cl.Client.RemoteEndPoint).Address);
            CommandRecieved(command);
        }

        private void OnRecieveData(IAsyncResult a)
        {
            TcpListener li = (TcpListener)a.AsyncState;
            TcpClient cl = li.EndAcceptTcpClient(a);

            try
            {
                NetworkStream serverstream = cl.GetStream();
                if (serverstream.CanRead)
                {
                    _data = new DataArguments();
                    _data.Data = ((IPEndPoint)cl.Client.RemoteEndPoint).Address.ToString();
                    int bytesread;
                    while (true)
                    {
                        Byte[] incoming = new byte[1024];
                        bytesread = 0;
                        try //getting actual siize of bytes from the stream
                        {
                            bytesread = serverstream.Read(incoming, 0, incoming.Length);
                            MemoryStream ms = new MemoryStream(incoming);
                            BinaryFormatter f = new BinaryFormatter();
                            ms.Write(incoming, 0, bytesread);
                            ms.Position = 0;
                            _data = (DataArguments)f.Deserialize(ms);
                            DataChanged(_data);
                            commandT.Start(_data);
                        }
                        catch
                        {
                            throw new SystemException("Recieving data error.");
                        }
                    }

                }
            }
            catch (Exception)
            {
                throw new SystemException("Error getting network stream.");
            }
        }


        /// <summary>
        /// The Data to send to the connected clients
        /// </summary>
        /// <param name="data">Data to send.  Type is Tdata which is a custom data class</param>
        public void senddata(object o)
        {
            IPAddress ip;
            try
            {
                ip = (IPAddress)o;
            }
            catch
            {
                throw new InvalidCastException("Could not cast to an IP address");
            }
            TcpClient client = new TcpClient();
            if (_data.DataArgs != null)
            {
                clientconnected.Reset();
                client.BeginConnect(ip, _dataport, new AsyncCallback(SendCommand), client);
                clientconnected.WaitOne();
                client.Close();
            }
            else
            {
                throw new ArgumentNullException();
            }
        }

        public void SendCommand(IAsyncResult a)
        {
            TcpClient client = (TcpClient)a.AsyncState;
            client.EndConnect(a);
            if (_isconnected == true)                            // check is the server is connected
            {
                NetworkStream ns = _c_c.GetStream();       // get the stream from server
                if (ns.CanWrite)                         // check if we can write to server stream
                {
                    ASCIIEncoding encoder = new ASCIIEncoding();
                    byte[] command = encoder.GetBytes(_command);    // convert the word stop into byte
                    ns.Write(command, 0, command.Length);       // send it to the server stream
                    ns.Flush();
                }
            }
        }

        public void sendCallback(IAsyncResult a)
        {

            TcpClient client = (TcpClient)a.AsyncState;
            client.EndConnect(a);
            NetworkStream stream = client.GetStream();
            if (stream.CanWrite)
            {
                Byte[] send;
                using (MemoryStream ms = new MemoryStream())
                {
                    BinaryFormatter bf = new BinaryFormatter();
                    try { bf.Serialize(ms, _data); }
                    catch { return; }
                    send = ms.ToArray();
                }
                stream.Write(send, 0, send.Length);
                stream.Close();
            }
            clientconnected.Set();
        }

        public ManualResetEvent clientconnected = new ManualResetEvent(false);

        //event fires when data is changed
        /// <summary>
        /// Delegate procedure which will handle the event
        /// </summary>
        /// <param name="o"></param>
        /// <param name="args">The arguments provided in the recieved data</param>
        public delegate void DataChangeHandler(DataArguments args);

        //the event
        public event DataChangeHandler DataChanged;

        public delegate void OnCommandRecieved(string Command);
        public event OnCommandRecieved CommandRecieved;

        public delegate void ClientbroadcastAsyncHandler(object userState);
        public void ClientBroadcastAsync(object userState)
        {
            ClientBroadcastEventArgs arg = new ClientBroadcastEventArgs(null);
            if (Ia == null)
            {
                Console.WriteLine("No clients connected.");
                arg.Message = "Broadcast did not start";
            }
            else
            {
                foreach (IPAddress ip in Ia)
                {
                    try
                    {
                        _data.DataArgs = (string)userState;
                        Thread t = new Thread(new ParameterizedThreadStart(senddata));
                        t.Start(ip);
                        arg.Message = string.Format("Broadcast to {0}", ip.ToString());
                        ClientBroadcastCompleted(userState, arg);
                    }
                    catch (Exception ex)
                    {
                        arg.Message = ex.Message;
                        ClientBroadcastCompleted(userState, arg);
                    }
                }
            }

            //_command = null;
        }

        public delegate void ClientBroadcastCompletedHandler(object sender, ClientBroadcastEventArgs arg);
        public event ClientBroadcastCompletedHandler ClientBroadcastCompleted;


    }

    public class ClientBroadcastEventArgs : EventArgs
    {
        private string _msg;

        public string Message
        {
            get { return _msg; }
            set { _msg = value; }
        }

        public ClientBroadcastEventArgs(string s)
        {
            _msg = s;
        }
    }
}



